/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tdom-internal.h"
#include "tdom.h"
#include "tdom_writer.h"
#include "minunit.h"

int tests_run = 0;
int assertion = 0;
const char *last_test = "";
int line = 0;
int strdup_fail = 0;

static struct tdom_doc *doc = NULL;

#define TEST_XML \
    "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \
    "<root xmlns=\"http://abc.com/ns/default/\"\r\n" \
    "      xmlns:ns1=\"http://abc.com/ns/default/\"\r\n" \
    "      xmlns:dc=\"http://abc.com/ns/dc/\"\r\n" \
    "      restricted=\"1\">\r\n" \
    "  <elem1 ns1:name=\"mary\" dc:name=\"nancy\" age=\"30\">\r\n" \
    "    value\r\n" \
    "  </elem1>\r\n" \
    "  aaa\r\n" \
    "  <ns1:elem1>\r\n" \
    "  </ns1:elem1>\r\n" \
    "  <elem1 xmlns=\"http://abc.com/ns/default/\"/>\r\n" \
    "  <elem1 xmlns:ns2=\"http://abc.com/ns/ns2/\"\r\n" \
    "         xmlns=\"http://abc.com/ns/default2/\">\r\n" \
    "  </elem1>\r\n" \
    "  <elem1 xmlns=\"http://abc.com/ns/dc/\">\r\n" \
    "  </elem1>\r\n" \
    "  <elem2>\r\n" \
    "    <ns1:elem2_1 xmlns:ns1=\"http://abc.com/ns/dc/\">\r\n" \
    "    </ns1:elem2_1>\r\n" \
    "    <elem2_2 xmlns:ns1=\"http://abc.com/ns/dc/\">\r\n" \
    "      <ns1:elem2_2_1 />\r\n" \
    "    </elem2_2>\r\n" \
    "  </elem2>\r\n" \
    "</root>\r\n" \

#define NUM_ROOT_CHILDREN       7
#define NUM_ROOT_ATTRS          1
#define NUM_ROOT_NSS            3

#ifdef __strdup
#undef __strdup
char *__strdup(const char *s)
#else
char *strdup(const char *s)
#endif
{
    char *p;
    int len;
    if (strdup_fail > 0) {
        strdup_fail--;
        return NULL;
    }
    if (strdup_fail < 0) {
        strdup_fail++;
        if (strdup_fail == 0) {
            return NULL;
        }
    }
    len = strlen(s);
    if ((p = malloc(len + 1))) {
        memcpy(p, s, len + 1);
    }
    return p;
}

static
struct tdom_doc *load_doc(const char *buff, int len)
{
    int ret;
    struct tdom_parser *parser;
    struct tdom_doc *result = NULL;
   
    if ((parser = tdom_parser_new())) {
        ret =  tdom_parser_parse(parser, buff, len);
        if (ret > 0) {
            result = tdom_parser_get_doc(parser);
        }
        tdom_parser_delete(parser);
    }

    return result;
}

static
const char *test_doc_new()
{
    struct tdom_doc *doc;
    
    doc = tdom_doc_new(NULL, NULL, NULL);
    mu_assert("new doc", doc != NULL);
    mu_assert("doc root", doc->root == NULL);
    mu_assert("doc version", doc->version == NULL);
    mu_assert("doc encoding", doc->encoding == NULL);
    tdom_doc_delete(doc);

    doc = tdom_doc_new((struct tdom_elem *)1, "1.0", "utf-8");
    mu_assert("new doc", doc != NULL);
    mu_assert("doc root", doc->root == (struct tdom_elem *)1);
    mu_assert("doc version", doc->version != NULL);
    mu_assert("doc version", strcmp("1.0", doc->version) == 0);
    mu_assert("doc encoding", doc->encoding != NULL);
    mu_assert("doc encoding", strcmp("utf-8", doc->encoding) == 0);
    doc->root = NULL;
    tdom_doc_delete(doc);

    strdup_fail = 1;
    doc = tdom_doc_new((struct tdom_elem *)1, "1.0", "utf-8");
    mu_assert("new doc", doc == NULL);

    return 0;
}

static
const char *test_elem_new()
{
    struct tdom_elem *elem;

    elem = tdom_elem_new(NULL, NULL);
    mu_assert("new elem", elem != NULL);
    mu_assert("node type", elem->node.type == TDOM_ELEM);
    mu_assert("node parent", elem->node.parent == NULL);
    mu_assert("node name", elem->node.name == NULL);
    mu_assert("elem children", TAILQ_EMPTY(&elem->children));
    mu_assert("elem attrs", TAILQ_EMPTY(&elem->attrs));
    mu_assert("elem nss", TAILQ_EMPTY(&elem->nss));
    tdom_elem_delete(elem);

    elem = tdom_elem_new((struct tdom_elem *)1, NULL);
    mu_assert("new elem", elem != NULL);
    mu_assert("node type", elem->node.type == TDOM_ELEM);
    mu_assert("node parent", elem->node.parent == (struct tdom_elem *)1);
    mu_assert("node name", elem->node.name == NULL);
    mu_assert("elem children", TAILQ_EMPTY(&elem->children));
    mu_assert("elem attrs", TAILQ_EMPTY(&elem->attrs));
    mu_assert("elem nss", TAILQ_EMPTY(&elem->nss));
    tdom_elem_delete(elem);

    elem = tdom_elem_new(NULL, "name");
    mu_assert("new elem", elem != NULL);
    mu_assert("node type", elem->node.type == TDOM_ELEM);
    mu_assert("node parent", elem->node.parent == NULL);
    mu_assert("node name", elem->node.name != NULL);
    mu_assert("node name", strcmp("name", elem->node.name) == 0);
    mu_assert("elem children", TAILQ_EMPTY(&elem->children));
    mu_assert("elem attrs", TAILQ_EMPTY(&elem->attrs));
    mu_assert("elem nss", TAILQ_EMPTY(&elem->nss));
    tdom_elem_delete(elem);

    strdup_fail = 1;
    mu_assert("strdup failure", tdom_elem_new(NULL, "name") == NULL);

    return 0;
}

static
const char *test_attr_new()
{
    struct tdom_attr *attr;

    attr = tdom_attr_new(NULL, NULL, NULL);
    mu_assert("new attr", attr != NULL);
    mu_assert("node type", attr->node.type == TDOM_ATTR);
    mu_assert("node parent", attr->node.parent == NULL);
    mu_assert("node name", attr->node.name == NULL);
    mu_assert("attr value", attr->value == NULL);
    tdom_attr_delete(attr);

    attr = tdom_attr_new((struct tdom_elem *)1, "name", "value");
    mu_assert("new attr", attr != NULL);
    mu_assert("node type", attr->node.type == TDOM_ATTR);
    mu_assert("node parent", attr->node.parent == (struct tdom_elem *)1);
    mu_assert("node name", attr->node.name != NULL);
    mu_assert("node name", strcmp("name", attr->node.name) == 0);
    mu_assert("attr value", attr->value != NULL);
    mu_assert("attr value", strcmp("value", attr->value) == 0);
    tdom_attr_delete(attr);

    strdup_fail = 1;
    mu_assert("strdup failure", tdom_attr_new(NULL, "name", "value") == NULL);
    strdup_fail = -2;
    mu_assert("strdup failure", tdom_attr_new(NULL, "name", "value") == NULL);

    return 0;
}

static
const char *test_text_new()
{
    struct tdom_text *text;

    text = tdom_text_new(NULL, NULL);
    mu_assert("new text", text != NULL);
    mu_assert("node type", text->node.type == TDOM_TEXT);
    mu_assert("node parent", text->node.parent == NULL);
    mu_assert("node name", text->node.name == NULL);
    mu_assert("text value", text->value == NULL);
    tdom_text_delete(text);

    text = tdom_text_new((struct tdom_elem *)1, "value");
    mu_assert("new text", text != NULL);
    mu_assert("node type", text->node.type == TDOM_TEXT);
    mu_assert("node parent", text->node.parent == (struct tdom_elem *)1);
    mu_assert("node name", text->node.name == NULL);
    mu_assert("text value", text->value != NULL);
    mu_assert("text value", strcmp("value", text->value) == 0);
    tdom_text_delete(text);

    strdup_fail = 1;
    mu_assert("strdup failure", tdom_text_new(NULL, "value") == NULL);

    return 0;
}

static
const char *test_ns_new()
{
    struct tdom_ns *ns;

    ns = tdom_ns_new(NULL, NULL);
    mu_assert("new ns", ns != NULL);
    mu_assert("ns prefix", ns->prefix == NULL);
    mu_assert("ns uri", ns->uri == NULL);
    tdom_ns_delete(ns);

    ns = tdom_ns_new("prefix", "uri");
    mu_assert("new ns", ns != NULL);
    mu_assert("ns prefix", ns->prefix != NULL);
    mu_assert("ns prefix", strcmp("prefix", ns->prefix) == 0);
    mu_assert("ns uri", ns->uri != NULL);
    mu_assert("ns uri", strcmp("uri", ns->uri) == 0);
    tdom_ns_delete(ns);

    mu_assert("1st param check", tdom_ns_new(NULL, "uri") == NULL);
    mu_assert("2nd param check", tdom_ns_new("prefix", NULL) == NULL);

    strdup_fail = 1;
    mu_assert("strdup failure", tdom_ns_new("prefix", "uri") == NULL);
    strdup_fail = -2;
    mu_assert("strdup failure", tdom_ns_new("prefix", "uri") == NULL);

    return 0;
}

static
const char *test_doc_set()
{
    struct tdom_doc *doc;

    mu_assert("set doc param", tdom_doc_set(NULL, NULL, NULL) == -1);
    mu_assert("set doc param", tdom_doc_set(NULL, "1.1", "utf-8") == -1);

    doc = tdom_doc_new(NULL, NULL, NULL);
    mu_assert("new doc", doc != NULL);

    mu_assert("set doc param", tdom_doc_set(doc, "1.1", "utf-8") == 0);
    mu_assert("doc version", doc->version != NULL);
    mu_assert("doc version", strcmp("1.1", doc->version) == 0);
    mu_assert("doc encoding", doc->encoding != NULL);
    mu_assert("doc encoding", strcmp("utf-8", doc->encoding) == 0);

    mu_assert("set doc param", tdom_doc_set(doc, NULL, "utf-8") == 0);
    mu_assert("doc version", doc->version == NULL);
    mu_assert("doc encoding", doc->encoding != NULL);
    mu_assert("doc encoding", strcmp("utf-8", doc->encoding) == 0);

    mu_assert("set doc param", tdom_doc_set(doc, "1.1", NULL) == 0);
    mu_assert("doc version", doc->version != NULL);
    mu_assert("doc version", strcmp("1.1", doc->version) == 0);
    mu_assert("doc encoding", doc->encoding == NULL);

    mu_assert("set doc param", tdom_doc_set(doc, NULL, NULL) == 0);
    mu_assert("doc version", doc->version == NULL);
    mu_assert("doc encoding", doc->encoding == NULL);

    strdup_fail = 1;
    mu_assert("set doc param", tdom_doc_set(doc, "1.1", "utf-8") == -2);
    mu_assert("doc version", doc->version == NULL);
    mu_assert("doc encoding", doc->encoding == NULL);
    strdup_fail = -2;
    mu_assert("set doc param", tdom_doc_set(doc, "1.1", "utf-8") == -2);
    mu_assert("doc version", doc->version == NULL);
    mu_assert("doc encoding", doc->encoding == NULL);

    tdom_doc_delete(doc);

    return 0;
}

static
const char *test_append_child()
{
    struct tdom_elem *root;
    struct tdom_elem *child1;
    struct tdom_elem *child2;
    struct tdom_node *node;

    root = tdom_elem_new(NULL, "root");
    mu_assert("tdom_elem_new", root != NULL);

    child1 = tdom_elem_new(NULL, "child1");
    mu_assert("tdom_elem_new", child1 != NULL);

    child2 = tdom_elem_new(NULL, "child2");
    mu_assert("tdom_elem_new", child2 != NULL);

    tdom_append_child(root, &child1->node);
    mu_assert("parent check", child1->node.parent == root);
    node = TAILQ_LAST(&root->children, tdom_node_list);
    mu_assert("child check", node == &child1->node);

    tdom_append_child(root, &child2->node);
    mu_assert("parent check", child2->node.parent == root);
    node = TAILQ_LAST(&root->children, tdom_node_list);
    mu_assert("child check", node == &child2->node);

    tdom_elem_delete(root);

    return 0;
}

static
const char *test_insert_after()
{
    struct tdom_elem *root;
    struct tdom_elem *child1;
    struct tdom_elem *child2;
    struct tdom_elem *elem;
    struct tdom_node *node;

    root = tdom_elem_new(NULL, "root");
    mu_assert("tdom_elem_new", root != NULL);

    child1 = tdom_elem_new(NULL, "child1");
    mu_assert("tdom_elem_new", child1 != NULL);

    child2 = tdom_elem_new(NULL, "child2");
    mu_assert("tdom_elem_new", child2 != NULL);

    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);

    tdom_append_child(root, &child1->node);
    tdom_append_child(root, &child2->node);
    tdom_insert_after(root, &elem->node, &child1->node);

    node = TAILQ_FIRST(&root->children);
    node = TAILQ_NEXT(node, sibling);

    mu_assert("inserted child", node == &elem->node);

    tdom_elem_delete(root);

    return 0;
}

static
const char *test_remove_child()
{
    struct tdom_elem *root;
    struct tdom_elem *child;
    struct tdom_node *node;

    root = tdom_elem_new(NULL, "root");
    mu_assert("tdom_elem_new", root != NULL);

    child = tdom_elem_new(NULL, "child");
    mu_assert("tdom_elem_new", child != NULL);

    tdom_append_child(root, &child->node);
    node = TAILQ_FIRST(&root->children);
    mu_assert("inserted child", node == &child->node);

    tdom_remove_child(root, &child->node);
    node = TAILQ_FIRST(&root->children);
    mu_assert("no child", node == NULL);

    tdom_elem_delete(root);
    tdom_elem_delete(child);

    return 0;
}

static
const char *test_set_value()
{
    struct tdom_elem *elem;
    int ret;
    const char *value;

    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);

    ret = tdom_set_value(elem, "value");
    mu_assert("set value", ret == 0);
    value = tdom_get_value(elem);
    mu_assert("check value", strcmp(value, "value") == 0);

    ret = tdom_set_value(elem, "new value");
    mu_assert("set value", ret == 0);
    value = tdom_get_value(elem);
    mu_assert("check value", strcmp(value, "new value") == 0);

    ret = tdom_set_value(elem, NULL);
    mu_assert("param check", ret == -1);
    value = tdom_get_value(elem);
    mu_assert("check value", strcmp(value, "new value") == 0);

    ret = tdom_set_value(NULL, "other value");
    mu_assert("param check", ret == -1);
    value = tdom_get_value(elem);
    mu_assert("check value", strcmp(value, "new value") == 0);

    ret = tdom_set_value(NULL, NULL);
    mu_assert("param check", ret == -1);
    value = tdom_get_value(elem);
    mu_assert("check value", strcmp(value, "new value") == 0);

    strdup_fail = 1;
    ret = tdom_set_value(elem, "other value");
    mu_assert("set value", ret == -2);
    value = tdom_get_value(elem);
    mu_assert("check value", strcmp(value, "new value") == 0);

    tdom_elem_delete(elem);

    return 0;
}

static
const char *test_get_attr()
{
    struct tdom_elem *elem;
    struct tdom_attr *attr;

    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);

    attr = tdom_attr_new(elem, "name", "value");
    mu_assert("tdom_attr_new", attr != NULL);

    TAILQ_INSERT_TAIL(&elem->attrs, &attr->node, sibling);

    mu_assert("get attr", tdom_get_attr(elem, "name", NULL) != NULL);
    mu_assert("get attr", strcmp("value", tdom_get_attr(elem, "name", NULL)) == 0);

    mu_assert("invalid arg", tdom_get_attr(NULL, NULL, NULL) == NULL);
    mu_assert("invalid arg", tdom_get_attr(NULL, "name", NULL) == NULL);
    mu_assert("invalid arg", tdom_get_attr(elem, NULL, NULL) == NULL);

    tdom_elem_delete(elem);

    return 0;
}

static
const char *test_set_attr()
{
    struct tdom_elem *elem;
    struct tdom_ns *ns;
    int count;

    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);

    mu_assert("invalid arg", tdom_set_attr(NULL, "name", "value", NULL) == -1);
    mu_assert("invalid arg", tdom_set_attr(elem, NULL, "value", NULL) == -1);
    mu_assert("invalid arg", tdom_set_attr(elem, "name", NULL, NULL) == -1);

    mu_assert("set attr", tdom_set_attr(elem, "name", "0", NULL) == 0);
    mu_assert("get attr", tdom_get_attr(elem, "name", NULL) != NULL);
    mu_assert("get attr", strcmp("0", tdom_get_attr(elem, "name", NULL)) == 0);

    mu_assert("update attr", tdom_set_attr(elem, "name", "1", NULL) == 0);
    mu_assert("get attr", tdom_get_attr(elem, "name", NULL) != NULL);
    mu_assert("get attr", strcmp("1", tdom_get_attr(elem, "name", NULL)) == 0);

    /* namespace */
    mu_assert("namespace", TAILQ_FIRST(&elem->nss) == NULL);
    mu_assert("set attr", tdom_set_attr(elem, "name", "0", "uri1") == 0);
    mu_assert("get attr", tdom_get_attr(elem, "name", "uri1") != NULL);
    mu_assert("get attr", strcmp("0", tdom_get_attr(elem, "name", "uri1")) == 0);
    mu_assert("namespace", TAILQ_FIRST(&elem->nss) != NULL);
    mu_assert("namespace", TAILQ_FIRST(&elem->nss)->uri != NULL);
    mu_assert("namespace", strcmp("uri1", TAILQ_FIRST(&elem->nss)->uri) == 0);
    count = 0;
    TAILQ_FOREACH(ns, &elem->nss, sibling) { count++; }
    mu_assert("namespace", count == 1);

    mu_assert("set attr", tdom_set_attr(elem, "name", "1", "uri1") == 0);
    mu_assert("get attr", tdom_get_attr(elem, "name", "uri1") != NULL);
    mu_assert("get attr", strcmp("1", tdom_get_attr(elem, "name", "uri1")) == 0);
    mu_assert("namespace", TAILQ_FIRST(&elem->nss) != NULL);
    mu_assert("namespace", TAILQ_FIRST(&elem->nss)->uri != NULL);
    mu_assert("namespace", strcmp("uri1", TAILQ_FIRST(&elem->nss)->uri) == 0);
    count = 0;
    TAILQ_FOREACH(ns, &elem->nss, sibling) { count++; }
    mu_assert("namespace", count == 1);

    mu_assert("set attr", tdom_set_attr(elem, "name", "2", "uri2") == 0);
    mu_assert("get attr", tdom_get_attr(elem, "name", "uri1") != NULL);
    mu_assert("get attr", strcmp("1", tdom_get_attr(elem, "name", "uri1")) == 0);
    mu_assert("get attr", tdom_get_attr(elem, "name", "uri2") != NULL);
    mu_assert("get attr", strcmp("2", tdom_get_attr(elem, "name", "uri2")) == 0);
    mu_assert("namespace", TAILQ_FIRST(&elem->nss) != NULL);
    mu_assert("namespace", TAILQ_FIRST(&elem->nss)->uri != NULL);
    mu_assert("namespace", strcmp("uri1", TAILQ_FIRST(&elem->nss)->uri) == 0);
    mu_assert("namespace", TAILQ_LAST(&elem->nss, tdom_ns_list) != NULL);
    mu_assert("namespace", TAILQ_LAST(&elem->nss, tdom_ns_list)->uri != NULL);
    mu_assert("namespace", strcmp("uri2", TAILQ_LAST(&elem->nss, tdom_ns_list)->uri) == 0);
    count = 0;
    TAILQ_FOREACH(ns, &elem->nss, sibling) { count++; }
    mu_assert("namespace", count == 2);

    strdup_fail = 1;
    mu_assert("set attr", tdom_set_attr(elem, "name", "value", "uri2") == -2);
    strdup_fail = 1;
    mu_assert("set attr", tdom_set_attr(elem, "other", "value", "uri3") == -2);
    strdup_fail = -1;
    mu_assert("set attr", tdom_set_attr(elem, "other", "value", "uri3") == -2);
    strdup_fail = -2;
    mu_assert("set attr", tdom_set_attr(elem, "other", "value", "uri3") == -2);
    strdup_fail = -3;
    mu_assert("set attr", tdom_set_attr(elem, "other", "value", "uri3") == -2);

    tdom_elem_delete(elem);

    /* max attr ns */
    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);
    for (count = 0; count <= TDOM_GEN_PREFIX_MAX + 1; count++) {
        char buff[8];
        snprintf(buff, sizeof(buff), "a_%d", count);
        if (count <= TDOM_GEN_PREFIX_MAX) {
            mu_assert("set attr", tdom_set_attr(elem, buff, "value", buff) == 0);
        } else {
            mu_assert("set attr", tdom_set_attr(elem, buff, "value", buff) != 0);
        }
    }
    tdom_elem_delete(elem);

    /* max attr ns (same uri) */
    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);
    for (count = 0; count <= TDOM_GEN_PREFIX_MAX + 1; count++) {
        char buff[8];
        snprintf(buff, sizeof(buff), "a_%d", count);
        mu_assert("set attr", tdom_set_attr(elem, buff, "value", "uri") == 0);
    }
    tdom_elem_delete(elem);

    return 0;
}

static
const char *test_remove_attr()
{
    struct tdom_elem *elem;

    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);

    mu_assert("set attr", tdom_set_attr(elem, "name", "value", NULL) == 0);

    mu_assert("not empy", !TAILQ_EMPTY(&elem->attrs));
    tdom_remove_attr(elem, "name", NULL);
    mu_assert("empty", TAILQ_EMPTY(&elem->attrs));

    /* namespace */
    mu_assert("set attr", tdom_set_attr(elem, "name", "value", "uri") == 0);

    mu_assert("not empy", !TAILQ_EMPTY(&elem->attrs));
    tdom_remove_attr(elem, "name", NULL);
    mu_assert("not empy", !TAILQ_EMPTY(&elem->attrs));
    tdom_remove_attr(elem, "name", "uri");
    mu_assert("empty", TAILQ_EMPTY(&elem->attrs));

    tdom_elem_delete(elem);

    return 0;
}

static
const char *test_set_ns()
{
    struct tdom_elem *elem;

    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);

    mu_assert("invalid arg", tdom_set_ns(NULL, NULL, NULL) == -1);
    mu_assert("invalid arg", tdom_set_ns(elem, NULL, NULL) == -1);
    mu_assert("invalid arg", tdom_set_ns(NULL, "ns", NULL) == -1);
    mu_assert("invalid arg", tdom_set_ns(NULL, NULL, "uri") == -1);
    mu_assert("invalid arg", tdom_set_ns(NULL, "ns", "uri") == -1);
    mu_assert("invalid arg", tdom_set_ns(elem, "ns", NULL) == -1);
    mu_assert("invalid arg", tdom_set_ns(elem, NULL, "uri") == -1);

    mu_assert("set ns", tdom_set_ns(elem, "ns", "uri") == 0);
    mu_assert("set ns", tdom_set_ns(elem, "ns", "uri") == 1);

    tdom_elem_delete(elem);

    return 0;
}

static
const char *test_set_elem_value()
{
    struct tdom_elem *elem;

    /* no ns_uri */
    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);

    mu_assert("set elem value", tdom_set_elem_value(elem, "tagname", "tagvalue", NULL) == 0);
    mu_assert("check the value", strcmp("tagvalue", tdom_get_value(tdom_get_elem(elem, "tagname", NULL, 0))) == 0);
    mu_assert("count elem", tdom_count_elem(elem, "tagname", NULL) == 1);

    mu_assert("set elem value", tdom_set_elem_value(elem, "tagname", "newvalue", NULL) == 0);
    mu_assert("check the value", strcmp("newvalue", tdom_get_value(tdom_get_elem(elem, "tagname", NULL, 0))) == 0);
    mu_assert("count elem", tdom_count_elem(elem, "tagname", NULL) == 1);

    mu_assert("set elem value", tdom_set_elem_value(NULL, "tagname", "newvalue", NULL) == -1);
    mu_assert("set elem value", tdom_set_elem_value(elem, NULL, "newvalue", NULL) == -1);
    mu_assert("set elem value", tdom_set_elem_value(elem, "tagname", NULL, NULL) == -1);
    mu_assert("set elem value", tdom_set_elem_value(NULL, NULL, NULL, NULL) == -1);

    mu_assert("check the value", strcmp("newvalue", tdom_get_value(tdom_get_elem(elem, "tagname", NULL, 0))) == 0);
    mu_assert("count elem", tdom_count_elem(elem, "tagname", NULL) == 1);

    tdom_elem_delete(elem);


    /* ns_uri */
    elem = tdom_elem_new(NULL, "elem");
    mu_assert("tdom_elem_new", elem != NULL);

    mu_assert("set elem value", tdom_set_elem_value(elem, "tagname", "tagvalue", "uri") == 0);
    mu_assert("check the value", strcmp("tagvalue", tdom_get_value(tdom_get_elem(elem, "tagname", "uri", 0))) == 0);
    mu_assert("count elem", tdom_count_elem(elem, "tagname", "uri") == 1);

    mu_assert("set elem value", tdom_set_elem_value(elem, "tagname", "newvalue", "uri") == 0);
    mu_assert("check the value", strcmp("newvalue", tdom_get_value(tdom_get_elem(elem, "tagname", "uri", 0))) == 0);
    mu_assert("count elem", tdom_count_elem(elem, "tagname", "uri") == 1);

    mu_assert("set elem value", tdom_set_elem_value(elem, "other", "othervalue", "uri2") == 0);
    mu_assert("check the value", strcmp("othervalue", tdom_get_value(tdom_get_elem(elem, "other", "uri2", 0))) == 0);
    mu_assert("count elem", tdom_count_elem(elem, "tagname", "uri") == 1);
    mu_assert("count elem", tdom_count_elem(elem, "other", "uri2") == 1);

    strdup_fail = 1;
    mu_assert("set elem value", tdom_set_elem_value(elem, "abc", "def", "uri3") == -2);
    strdup_fail = -1;
    mu_assert("set elem value", tdom_set_elem_value(elem, "abc", "def", "uri3") == -2);
    strdup_fail = -2;
    mu_assert("set elem value", tdom_set_elem_value(elem, "abc", "def", "uri3") == -2);
    strdup_fail = -3;
    mu_assert("set elem value", tdom_set_elem_value(elem, "abc", "def", "uri3") == -2);

    tdom_elem_delete(elem);

    return 0;
}

static
const char *test_soap_new()
{
    int i;
    struct tdom_doc *doc;

    doc = tdom_soap_new("myservice:1", "help");
    mu_assert("soap new", doc != NULL);
    tdom_doc_delete(doc);

    mu_assert("invalid param", tdom_soap_new(NULL, NULL) == NULL);
    mu_assert("invalid param", tdom_soap_new(NULL, "help") == NULL);
    mu_assert("invalid param", tdom_soap_new("myservice:1", NULL) == NULL);

    strdup_fail = 1;
    mu_assert("soap new", tdom_soap_new("myservice:1", "help") == NULL);

    for (i = -1; i > -11; i--) {
        strdup_fail = i;
        mu_assert("soap new", tdom_soap_new("myservice1", "help") == NULL);
    }

    return 0;
}

static
const char *test_soap_set()
{
    struct tdom_doc *doc;
    const char *value;

    doc = tdom_soap_new("myservice1", "help");
    mu_assert("soap new", doc != NULL);

    mu_assert("soap set", tdom_soap_set(doc, "option", "-v") == 0);
    value = tdom_soap_get(doc, "option");
    mu_assert("soap get", value != NULL);
    mu_assert("soap get", strcmp(value, "-v") == 0);

    mu_assert("soap set", tdom_soap_set(doc, "option", "-h") == 0);
    value = tdom_soap_get(doc, "option");
    mu_assert("soap get", value != NULL);
    mu_assert("soap get", strcmp(value, "-h") == 0);

    mu_assert("soap set", tdom_soap_set(doc, "memo", "aaa") == 0);
    value = tdom_soap_get(doc, "memo");
    mu_assert("soap get", value != NULL);
    mu_assert("soap get", strcmp(value, "aaa") == 0);

    mu_assert("param check", tdom_soap_set(NULL, NULL, NULL) == -1);
    mu_assert("param check", tdom_soap_set(NULL, "option", "-v") == -1);
    mu_assert("param check", tdom_soap_set(doc, NULL, "-v") == -1);
    mu_assert("param check", tdom_soap_set(doc, "option", NULL) == -1);

    value = tdom_soap_get(doc, "option");
    mu_assert("soap get", value != NULL);
    mu_assert("soap get", strcmp(value, "-h") == 0);

    mu_assert("soap set", tdom_soap_set(doc, "memo", "aaa") == 0);
    value = tdom_soap_get(doc, "memo");
    mu_assert("soap get", value != NULL);
    mu_assert("soap get", strcmp(value, "aaa") == 0);

    tdom_doc_delete(doc);

    return 0;
}

static
const char *test_soap_set_pointer()
{
    struct tdom_doc *doc;
    int ret;
    char *value0;
    char *value1;

    value0 = strdup("value0");
    mu_assert("new value", value0 != NULL);
    value1 = strdup("value1");
    mu_assert("new value", value1 != NULL);

    doc = tdom_soap_new("myservice1", "help");
    mu_assert("soap new", doc != NULL);

    ret = tdom_soap_set_pointer(doc, "argument name", value0);
    mu_assert("set pointer", ret == 0);
    mu_assert("check value", strcmp("value0", tdom_soap_get(doc, "argument name")) == 0);

    ret = tdom_soap_set_pointer(doc, "argument name", value1);
    mu_assert("set pointer", ret == 0);
    mu_assert("check value", strcmp("value1", tdom_soap_get(doc, "argument name")) == 0);

    strdup_fail = -1;
    ret = tdom_soap_set_pointer(doc, "argument name", value1);
    mu_assert("set pointer", ret == -2);

    tdom_doc_delete(doc);

    return 0;
}

static
const char *test_soap_get()
{
    struct tdom_doc *doc;
    struct tdom_elem *elem;

    doc = tdom_soap_new("myservice:1", "help");
    mu_assert("soap new", doc != NULL);

    mu_assert("soap set", tdom_soap_set(doc, "arg0", "value0") == 0);
    mu_assert("soap set", tdom_soap_set(doc, "arg1", "value1") == 0);
    mu_assert("soap set", tdom_soap_set(doc, "arg2", "value2") == 0);

    mu_assert("soap get", strcmp(tdom_soap_get(doc, "arg0"), "value0") == 0);
    mu_assert("soap get", strcmp(tdom_soap_get(doc, "arg1"), "value1") == 0);
    mu_assert("soap get", strcmp(tdom_soap_get(doc, "arg2"), "value2") == 0);
    mu_assert("soap get", tdom_soap_get(doc, "not exists") == NULL);

    mu_assert("invalid param", tdom_soap_get(NULL, "arg0") == NULL);
    mu_assert("invalid param", tdom_soap_get(doc, NULL) == NULL);
    mu_assert("invalid param", tdom_soap_get(NULL, NULL) == NULL);

    elem = tdom_get_elem(doc->root, "Body", TDOM_SOAP_ENV_NS_URI, 0);
    mu_assert("s:Body", elem != NULL);
    tdom_remove_child(doc->root, &elem->node);
    tdom_elem_delete(elem);

    mu_assert("soap get", tdom_soap_get(doc, "arg0") == NULL);
    mu_assert("soap get", tdom_soap_get(doc, "arg1") == NULL);
    mu_assert("soap get", tdom_soap_get(doc, "arg2") == NULL);

    tdom_doc_delete(doc);

    return 0;
}

#define TEST_SOAP_ERROR_XML \
    "<?xml version=\"1.0\"?>\n" \
    "<s:Envelope\n" \
    "    xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"\n" \
    "    s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" \
    "  <s:Body>\n" \
    "    <s:Fault>\n" \
    "      <faultcode>s:Client</faultcode>\n" \
    "      <faultstring>UPnPError</faultstring>\n" \
    "      <detail>\n" \
    "        <UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n" \
    "          <errorCode>800</errorCode>\n" \
    "          <errorDescription>error string</errorDescription>\n" \
    "        </UPnPError>\n" \
    "      </detail>\n" \
    "    </s:Fault>\n" \
    "  </s:Body>\n" \
    "</s:Envelope>\n" \

static
const char *test_soap_err_new()
{
    const char *tmp;
    struct tdom_doc *doc;

    doc = tdom_soap_err_new(0, NULL);
    mu_assert("soap err new", doc != NULL);
    tdom_doc_delete(doc);

    doc = tdom_soap_err_new(800, "error string");
    mu_assert("soap err new", doc != NULL);

    tmp = tdom_soap_get_upnp_err(doc, "errorCode");
    mu_assert("errorCode", strcmp(tmp, "800") == 0);

    tmp = tdom_soap_get_upnp_err(doc, "errorDescription");
    mu_assert("errorDescription", strcmp(tmp, "error string") == 0);

    tdom_doc_delete(doc);

    return 0;
}

static
const char *test_soap_get_upnp_err()
{
    const char *tmp;
    struct tdom_doc *doc;

    doc = load_doc(TEST_SOAP_ERROR_XML, sizeof(TEST_SOAP_ERROR_XML) - 1);
    mu_assert("load_doc", doc != NULL);

    tmp = tdom_soap_get_upnp_err(doc, "errorCode");
    mu_assert("errorCode", strcmp(tmp, "800") == 0);

    tmp = tdom_soap_get_upnp_err(doc, "errorDescription");
    mu_assert("errorDescription", strcmp(tmp, "error string") == 0);

    tdom_doc_delete(doc);

    return 0;
}

static
const char *test_is_ns_default()
{
    mu_assert("default ns", tdom_is_ns_default(doc->root, "http://abc.com/ns/default/"));

    mu_assert("1st param check", tdom_is_ns_default(NULL, "http://abc.com/ns/default/") == 0);
    mu_assert("2nd param check", tdom_is_ns_default(doc->root, NULL) == 0);

    return 0;
}

static
const char *test_count_elem()
{
    mu_assert("elem count", tdom_count_elem(doc->root, "elem1", "http://abc.com/ns/default/") == 3);
    mu_assert("elem count", tdom_count_elem(doc->root, "elem1", "http://abc.com/ns/default2/") == 1);
    mu_assert("elem count", tdom_count_elem(doc->root, "elem1", NULL) == 4);
    mu_assert("elem count", tdom_count_elem(doc->root, "ns1:elem1", NULL) == 1);
    mu_assert("elem count", tdom_count_elem(doc->root, "elem1", "http://abc.com/ns/dc/") == 1);

    mu_assert("1st param check", tdom_count_elem(NULL, "elem1", "http://abc.com/ns/dc/") == 0);
    mu_assert("2nd param check", tdom_count_elem(doc->root, NULL, "http://abc.com/ns/dc/") == 0);

    return 0;
}

static
const char *test_count_ns_prefix()
{
    struct tdom_elem *elem;

    mu_assert("prefix count", tdom_count_ns_prefix(doc->root, "http://abc.com/ns/default/") == 2);
    mu_assert("prefix count", tdom_count_ns_prefix(doc->root, "http://abc.com/ns/dc/") == 1);

    mu_assert("1st param check", tdom_count_ns_prefix(NULL, "http://abc.com/ns/default/") == 0);
    mu_assert("2nd param check", tdom_count_ns_prefix(doc->root, NULL) == 0);

    elem = (struct tdom_elem *)TAILQ_LAST(&doc->root->children, tdom_node_list);
    mu_assert("last elem", elem != NULL);

    elem = (struct tdom_elem *)TAILQ_LAST(&elem->children, tdom_node_list);
    mu_assert("last elem", elem != NULL);

    mu_assert("prefix count", tdom_count_ns_prefix(elem, "http://abc.com/ns/dc/") == 2);

    return 0;
}

static
const char *test_get_attr2()
{
    struct tdom_elem *elem;
    const char *value;

    elem = (struct tdom_elem *)TAILQ_FIRST(&doc->root->children);
    mu_assert("first elem", elem != NULL);

    value = tdom_get_attr(elem, "name", "http://abc.com/ns/default/");
    mu_assert("attr value", value != NULL);
    mu_assert("attr value", strcmp("mary", value) == 0);

    value = tdom_get_attr(elem, "name", NULL);
    mu_assert("attr value", value == NULL);

    value = tdom_get_attr(elem, "name", "http://abc.com/ns/dc/");
    mu_assert("attr value", value != NULL);
    mu_assert("attr value", strcmp("nancy", value) == 0);

    value = tdom_get_attr(elem, "age", "http://abc.com/ns/default/");
    mu_assert("attr value", value != NULL);
    mu_assert("attr value", strcmp("30", value) == 0);

    return 0;
}

static
const char *test_get_value()
{
    struct tdom_elem *elem;
    const char *value;

    value = tdom_get_value(doc->root);
    mu_assert("elem value", value != NULL);
    mu_assert("elem value", strcmp("aaa", value) == 0);

    elem = (struct tdom_elem *)TAILQ_FIRST(&doc->root->children);
    mu_assert("first elem", elem != NULL);

    value = tdom_get_value(elem);
    mu_assert("elem value", value != NULL);
    mu_assert("elem value", strcmp("value", value) == 0);

    elem = (struct tdom_elem *)TAILQ_LAST(&doc->root->children, tdom_node_list);
    mu_assert("last elem", elem != NULL);
    mu_assert("elem value", tdom_get_value(elem) == NULL);

    mu_assert("1st param check", tdom_get_value(NULL) == NULL);

    return 0;
}

static
const char *test_lookup_ns_uri()
{
    struct tdom_elem *elem;
    const char *value;

    elem = (struct tdom_elem *)TAILQ_LAST(&doc->root->children, tdom_node_list);
    mu_assert("last elem", elem != NULL);

    value = tdom_lookup_ns_uri(elem, "ns1", 3);
    mu_assert("prefix", value != NULL);
    mu_assert("prefix", strcmp("http://abc.com/ns/default/", value) == 0);

    mu_assert("unknown prefix", tdom_lookup_ns_uri(elem, "none", 4) == NULL);

    mu_assert("1st param check", tdom_lookup_ns_uri(NULL, "ns1", 3) == NULL);
    mu_assert("2nd param check", tdom_lookup_ns_uri(elem, NULL, 3) == NULL);
    mu_assert("3rd param check", tdom_lookup_ns_uri(elem, "ns1", -1) == NULL);

    elem = (struct tdom_elem *)TAILQ_LAST(&elem->children, tdom_node_list);
    mu_assert("last elem", elem != NULL);
    mu_assert("elem name", elem->node.name != NULL);
    mu_assert("elem name", strcmp("elem2_2", elem->node.name) == 0);

    elem = (struct tdom_elem *)TAILQ_FIRST(&elem->children);
    mu_assert("last elem", elem != NULL);
    mu_assert("elem name", elem->node.name != NULL);
    mu_assert("elem name", strcmp("ns1:elem2_2_1", elem->node.name) == 0);

    value = tdom_lookup_ns_uri(elem, "ns1", 3);
    mu_assert("prefix", value != NULL);
    mu_assert("prefix", strcmp("http://abc.com/ns/dc/", value) == 0);

    value = tdom_lookup_ns_uri(elem, "dc", 2);
    mu_assert("prefix", value != NULL);
    mu_assert("prefix", strcmp("http://abc.com/ns/dc/", value) == 0);

    return 0;
}

static
const char *test_lookup_ns_prefix()
{
    struct tdom_elem *elem;
    const char *value;

    elem = (struct tdom_elem *)TAILQ_LAST(&doc->root->children, tdom_node_list);
    mu_assert("last elem", elem != NULL);

    value = tdom_lookup_ns_prefix(elem, "http://abc.com/ns/default/", 0);
    mu_assert("prefix", value != NULL);
    mu_assert("prefix", strcmp("", value) == 0);

    value = tdom_lookup_ns_prefix(elem, "http://abc.com/ns/default/", 1);
    mu_assert("prefix", value != NULL);
    mu_assert("prefix", strcmp("ns1", value) == 0);

    value = tdom_lookup_ns_prefix(elem, "http://abc.com/ns/default/", 2);
    mu_assert("prefix", value == NULL);

    mu_assert("unknown uri", tdom_lookup_ns_prefix(elem, "uri", 0) == NULL);

    mu_assert("1st param check", tdom_lookup_ns_prefix(NULL, "uri", 0) == NULL);
    mu_assert("2nd param check", tdom_lookup_ns_prefix(elem, NULL, 0) == NULL);
    mu_assert("3rd param check", tdom_lookup_ns_prefix(elem, "uri", -1) == NULL);

    return 0;
}

static
const char *test_get_elem()
{
    struct tdom_elem *elem;
    struct tdom_elem *child0;
    struct tdom_elem *child1;
    struct tdom_elem *child2;
    struct tdom_elem *child3;

    child0 = tdom_get_elem(doc->root, "elem1", "http://abc.com/ns/default/", 0);
    mu_assert("get elem", child0 != NULL);

    child1 = tdom_get_elem(doc->root, "elem1", "http://abc.com/ns/default/", 1);
    mu_assert("get elem", child1 != NULL);
    mu_assert("get elem", child1 != child0);

    child2 = tdom_get_elem(doc->root, "elem1", "http://abc.com/ns/default/", 2);
    mu_assert("get elem", child2 != NULL);
    mu_assert("get elem", child2 != child0);
    mu_assert("get elem", child2 != child1);

    child3 = tdom_get_elem(doc->root, "elem1", "http://abc.com/ns/default2/", 0);
    mu_assert("get elem", child3 != NULL);
    mu_assert("get elem", child3 != child0);
    mu_assert("get elem", child3 != child1);
    mu_assert("get elem", child3 != child2);

    elem = tdom_get_elem(doc->root, "elem1", "http://abc.com/ns/default/", 3);
    mu_assert("get elem", elem == NULL);

    elem = tdom_get_elem(doc->root, "elem1", NULL, 0);
    mu_assert("get elem", elem != NULL);
    mu_assert("get elem", elem == child0);

    elem = tdom_get_elem(doc->root, "elem1", NULL, 1);
    mu_assert("get elem", elem != NULL);
    mu_assert("get elem", elem == child2);

    elem = tdom_get_elem(doc->root, "elem1", NULL, 2);
    mu_assert("get elem", elem != NULL);

    elem = tdom_get_elem(doc->root, "elem1", NULL, 3);
    mu_assert("get elem", elem != NULL);

    elem = tdom_get_elem(doc->root, "elem1", NULL, 4);
    mu_assert("get elem", elem == NULL);

    elem = tdom_get_elem(doc->root, "ns1:elem1", NULL, 0);
    mu_assert("get elem", elem != NULL);

    elem = tdom_get_elem(doc->root, "ns1:elem1", NULL, 1);
    mu_assert("get elem", elem == NULL);

    mu_assert("1st param check", tdom_get_elem(NULL, "elem1", NULL, 0) == NULL);
    mu_assert("2nd param check", tdom_get_elem(doc->root, NULL, NULL, 0) == NULL);
    mu_assert("4th param check", tdom_get_elem(doc->root, "elem1", NULL, -1) == NULL);

    return 0;
}

static
const char *compare_elem(struct tdom_elem *parent1,
                         struct tdom_elem *parent2,
                         struct tdom_elem *elem1,
                         struct tdom_elem *elem2)
{
    const char *result;
    int n1;
    int n2;
    struct tdom_node *node;
    struct tdom_ns *ns;
    struct tdom_node *node1;
    struct tdom_node *node2;
    struct tdom_ns *ns1;
    struct tdom_ns *ns2;
    const char *value1;
    const char *value2;

    mu_assert("node type", elem1->node.type == elem2->node.type);
    mu_assert("node1 parent", elem1->node.parent == parent1);
    mu_assert("node2 parent", elem2->node.parent == parent2);
    mu_assert("node name", strcmp(elem1->node.name, elem2->node.name) == 0);

    n1 = n2 = 0;
    TAILQ_FOREACH(node, &elem1->children, sibling) { n1++; }
    TAILQ_FOREACH(node, &elem2->children, sibling) { n2++; }
    mu_assert("num of children", n1 == n2);

    TAILQ_FOREACH_2(node1, node2, &elem1->children, &elem2->children, sibling) {
        mu_assert("both node exist", node1 && node2);
        mu_assert("node type", node1->type == node2->type);
        if (node1->type == TDOM_ELEM) {
            result = compare_elem(elem1, elem2,
                                  (struct tdom_elem *)node1,
                                  (struct tdom_elem *)node2);
            mu_assert("compare elements", result == 0);
        } else if (node1->type == TDOM_TEXT) {
            value1 = ((struct tdom_text *)node1)->value;
            value2 = ((struct tdom_text *)node2)->value;
            mu_assert("both value exist", value1 && value2);
            mu_assert("text node value", strcmp(value1, value2) == 0);
            mu_assert("node1 parent", node1->parent = elem1);
            mu_assert("node2 parent", node2->parent = elem2);
        }
        mu_assert("no other", node1->type == TDOM_ELEM || node1->type == TDOM_TEXT);
    }

    n1 = n2 = 0;
    TAILQ_FOREACH(node, &elem1->attrs, sibling) { n1++; }
    TAILQ_FOREACH(node, &elem2->attrs, sibling) { n2++; }
    mu_assert("num of attrs", n1 == n2);

    TAILQ_FOREACH_2(node1, node2, &elem1->attrs, &elem2->attrs, sibling) {
        mu_assert("both node exist", node1 && node2);
        mu_assert("node1 type", node1->type == TDOM_ATTR);
        mu_assert("node2 type", node2->type == TDOM_ATTR);
        mu_assert("node1 parent", node1->parent = elem1);
        mu_assert("node2 parent", node2->parent = elem2);
        mu_assert("both name exist", node1->name && node2->name);
        mu_assert("attr name", strcmp(node1->name, node2->name) == 0);
        value1 = ((struct tdom_attr *)node1)->value;
        value2 = ((struct tdom_attr *)node2)->value;
        mu_assert("both value exist", value1 && value2);
        mu_assert("attr value", strcmp(value1, value2) == 0);
    }

    n1 = n2 = 0;
    TAILQ_FOREACH(ns, &elem1->nss, sibling) { n1++; }
    TAILQ_FOREACH(ns, &elem2->nss, sibling) { n2++; }
    mu_assert("num of nss", n1 == n2);

    TAILQ_FOREACH_2(ns1, ns2, &elem1->nss, &elem2->nss, sibling) {
        mu_assert("both ns exist", ns1 && ns2);
        mu_assert("both ns prefix exist", ns1->prefix && ns2->prefix);
        mu_assert("ns prefix", strcmp(ns1->prefix, ns2->prefix) == 0);
        mu_assert("both ns uri exist", ns1->uri && ns2->uri);
        mu_assert("ns uri", strcmp(ns1->uri, ns2->uri) == 0);
    }

    return NULL;
}

static
const char *test_parser_parse(int size)
{
    struct tdom_parser *parser;
    int ret;
    int pos;
    int len = sizeof(TEST_XML) - 1;
    const char *xml = TEST_XML;
    struct tdom_node *node;
    struct tdom_ns *ns;

    parser = tdom_parser_new();
    mu_assert("tdom_parser_new", parser != NULL);
    mu_assert("expects NULL", !tdom_parser_get_doc(parser));
    for (pos = 0; pos < len; pos += size) {
        size = min(size, len - pos);
        ret = tdom_parser_parse(parser, &xml[pos], size);
        if ((pos + size) >= (len - 2)) {
            mu_assert("tdom_parser_parse", ret == 1);
            break;
        } else {
            mu_assert("tdom_parser_parse", ret == 0);
            mu_assert("expects NULL", !tdom_parser_get_doc(parser));
        }
    }

    doc = tdom_parser_get_doc(parser);
    mu_assert("tdom_parser_get_doc", doc != NULL);
    mu_assert("doc has root", doc->root != NULL);

    mu_assert("no doc", tdom_parser_get_doc(parser) == NULL);
    tdom_parser_delete(parser);

    /* ---- */

    ret = 0;
    TAILQ_FOREACH(node, &doc->root->children, sibling) {
        ret++;
    }
    mu_assert("num of root children", ret == NUM_ROOT_CHILDREN);

    ret = 0;
    TAILQ_FOREACH(node, &doc->root->attrs, sibling) {
        ret++;
    }
    mu_assert("num of root attrs", ret == NUM_ROOT_ATTRS);

    ret = 0;
    TAILQ_FOREACH(ns, &doc->root->nss, sibling) {
        ret++;
    }
    mu_assert("num of root nss", ret == NUM_ROOT_NSS);

    return 0;
}

static
const char *test_doc_delete()
{
    tdom_doc_delete(NULL);
    tdom_doc_delete(doc);
    return 0;
}

static
const char *test_writer()
{
    int ret;
    const char *result;
    struct evbuffer *buffer;
    struct tdom_writer *writer;
    struct tdom_doc *orig_doc;
    struct tdom_doc *new_doc;
#ifndef USE_LIBEVENT_1
    char *tmp;
    size_t length;
#endif /* USE_LIBEVENT_1 */

    mu_assert("evbuffer_new", buffer = evbuffer_new());
    mu_assert("tdom_writer_new", writer = tdom_writer_new());

    orig_doc = load_doc(TEST_XML, sizeof(TEST_XML) - 1);
    mu_assert("load doc", orig_doc != NULL);

    tdom_writer_set(writer, orig_doc);
#if 0
    ret = tdom_writer_write_all(writer, buffer);
#else
    ret = tdom_writer_write_all(buffer, orig_doc);
#endif
    mu_assert("tdom_writer_write_all", ret > 0);

#ifdef USE_LIBEVENT_1
    new_doc = load_doc((char*)buffer->buffer, buffer->off);
#else /* USE_LIBEVENT_1 */
    length = evbuffer_get_length(buffer);
    tmp = (char*)malloc(length);
    evbuffer_copyout(buffer, tmp, length);
    new_doc = load_doc(tmp, length);
    free(tmp);
#endif /* USE_LIBEVENT_1 */
    mu_assert("load doc", new_doc != NULL);

    result = compare_elem(NULL, NULL, orig_doc->root, new_doc->root);

    tdom_doc_delete(orig_doc);
    tdom_doc_delete(new_doc);

    tdom_writer_delete(writer);
    evbuffer_free(buffer);

    return result;
}

static
const char *test_count_escaped_len()
{
    unsigned int i;

    struct {
        const char *text;
        int result;
    } list[] = {
        {"",            0},

        {"<",           4},
        {">",           4},
        {"&",           5},
        {"\"",          6},
        {"'",           6},

        {"<>&\"'",      (4 + 4 + 5 + 6 + 6)},

        {"a",           1},
        {"abc",         3},
        {"<abc",        (4 + 3)},  /* "&lt;abc" */
        {"a<bc",        (4 + 3)},  /* "a&lt;bc" */
        {"abc>",        (4 + 3)},  /* "abc&gt;" */
        {"<abc>",       (4 + 3 + 4)}, /* "&lt;abc&gt;" */
    };

    for (i = 0; i < mu_numelem(list); i++) {
        mu_assert("escaped len", tdom_count_escaped_len(list[i].text, strlen(list[i].text)) == list[i].result);
    }

    return 0;
}

static
const char *test_escape()
{
    unsigned int i;
    char buffer[64];
    int size;
    int ret;

    struct {
        const char *text;
        const char *result;
    } list[] = {
        {"<tag>",                   "&lt;tag&gt;"},
        {"<tag attr=\"value\">",    "&lt;tag attr=&quot;value&quot;&gt;"},
        {"<tag attr=\"v&'ue\">",    "&lt;tag attr=&quot;v&amp;&apos;ue&quot;&gt;"},
        {"<tag attr=\"v&'ue\">abc", "&lt;tag attr=&quot;v&amp;&apos;ue&quot;&gt;abc"},
    };

    for (i = 0; i < mu_numelem(list); i++) {
        int len = strlen(list[i].text);
        size = sizeof(buffer);
        ret = tdom_escape(list[i].text, len, buffer, &size);
        mu_assert("escape", ret == len);
        len = strlen(list[i].result);
        mu_assert("escape result", strncmp(list[i].result, buffer, len) == 0);
        mu_assert("escape result", sizeof(buffer) - size == len);
        mu_assert("escape result", tdom_count_escaped_len(list[i].text, strlen(list[i].text)) == len);
    }

    return 0;
}

static
const char *test_unescape()
{
    unsigned int i;
    char buffer[64];
    int len;
    int ret;

    struct {
        int expected;
        const char *text;
        const char *result;
        int len;
    } list[] = {
        {0, "&lt;",         "<",  1},
        {0, "&gt;",         ">",  1},
        {0, "&amp;",        "&",  1},
        {0, "&quot;",       "\"", 1},
        {0, "&apos;",       "'",  1},

        {1, "&" ,           "&",     1},
        {2, "&l",           "&l",    2},
        {3, "&lt",          "&lt",   3},
        {2, "&g",           "&g",    2},
        {3, "&gt",          "&gt",   3},
        {2, "&a",           "&a",    2},
        {3, "&am",          "&am",   3},
        {4, "&amp",         "&amp",  4},
        {2, "&q",           "&q",    2},
        {3, "&qu",          "&qu",   3},
        {4, "&quo",         "&quo",  4},
        {5, "&quot",        "&quot", 5},
        {2, "&a",           "&a",    2},
        {3, "&ap",          "&ap",   3},
        {4, "&apo",         "&apo",  4},
        {5, "&apos",        "&apos", 5},

        {0, " &lt;",        " <",  2},
        {0, " &gt;",        " >",  2},
        {0, " &amp;",       " &",  2},
        {0, " &quot;",      " \"", 2},
        {0, " &apos;",      " '",  2},

        {0, "&lt; ",         "< ",  2},
        {0, "&gt; ",         "> ",  2},
        {0, "&amp; ",        "& ",  2},
        {0, "&quot; ",       "\" ", 2},
        {0, "&apos; ",       "' ",  2},

        {1, "&lt;&",         "<&",  2},
        {1, "&gt;&",         ">&",  2},
        {1, "&amp;&",        "&&",  2},
        {1, "&quot;&",       "\"&", 2},
        {1, "&apos;&",       "'&",  2},

        {0, "&&lt;",        "&<",  2},
        {0, "&&gt;",        "&>",  2},
        {0, "&&amp;",       "&&",  2},
        {0, "&&quot;",      "&\"", 2},
        {0, "&&apos;",      "&'",  2},

        {0, "& ",           "& ",     2},
        {0, "&a ",          "&a ",    3},
        {0, "&ap ",         "&ap ",   4},
        {0, "&apo ",        "&apo ",  5},
        {0, "&apos ",       "&apos ", 6},

        {0, "&lt;tag&gt;",                                    "<tag>",   5},
        {3, "&lt;tag&gt",                                     "<tag&gt", 7},
        {0, "&lt;tag attr=&quot;value&quot;&gt;",             "<tag attr=\"value\">", 18},
        {0, "&lt;tag attr=&quot;v&amp;&apos;ue&quot;&gt;",    "<tag attr=\"v&'ue\">", 18},
        {0, "&lt;tag attr=&quot;v&amp;&apos;ue&quot;&gt;abc", "<tag attr=\"v&'ue\">abc", 21},
    };

    for (i = 0; i < mu_numelem(list); i++) {
        len = strlen(list[i].text);
        memcpy(buffer, list[i].text, len);
        ret = tdom_unescape(buffer, &len);
        //printf("%s : ret = %d, len = %d\n", list[i].text, ret, len);
        mu_assert("unescape", ret == list[i].expected);
        mu_assert("unescape result", len == strlen(list[i].result));
        mu_assert("unescape result", strncmp(buffer, list[i].result, len) == 0);
    }


    return 0;
}

struct skip_testdata {
    const char *text;
    const char *result;
    int len;
};

static
const char *skip_check_result(struct skip_testdata *list,
                              int size,
                              const char *(*func)(const char *, int *))
{
    int i;
    int len;
    const char *p;

    for (i = 0; i < size; i++) {
        len = strlen(list[i].text);
        p = func(list[i].text, &len);
        mu_assert("length check", len == list[i].len);
        if (p) {
            mu_assert("length check", len == strlen(list[i].result));
            mu_assert("result check", strncmp(p, list[i].result, len) == 0);
        } else {
            mu_assert("result check", list[i].result == NULL);
        }
    }

    return 0;
}

static
const char *test_ignore_space()
{
    struct skip_testdata list[] = {
        {"",                    NULL,       0},
        {"hoge",                "hoge",     4},
        {"   hoge   ",          "hoge",     4},
        {"hoge   ",             "hoge",     4},
        {"   hoge",             "hoge",     4},
        {"   ho ge   ",         "ho ge",    5},
        {"   ho\r\nge   ",      "ho\r\nge", 6},
        {"\r\nhoge\r\n",        "hoge",     4},
        {"\r\n  hoge  \r\n",    "hoge",     4},
        {"  \r\nhoge\r\n  ",    "hoge",     4},
        {" \r\n hoge \r\n ",    "hoge",     4},
        {" \r \n hoge \r \n ",  "hoge",     4},
    };

    return skip_check_result(list, mu_numelem(list), ignore_space);
}

static
const char *test_skip_head_space()
{
    int len;
    struct skip_testdata list[] = {
        {"",                    NULL,          0},
        {"hoge",                "hoge",        4},
        {"   hoge   ",          "hoge   ",     7},
        {"hoge   ",             "hoge   ",     7},
        {"   hoge",             "hoge",        4},
        {"   ho ge   ",         "ho ge   ",    8},
        {"   ho\r\nge   ",      "ho\r\nge   ", 9},
        {"\r\nhoge\r\n",        "hoge\r\n",    6},
        {"\r\n  hoge  \r\n",    "hoge  \r\n",  8},
        {"  \r\nhoge\r\n  ",    "hoge\r\n  ",  8},
        {" \r\n hoge \r\n ",    "hoge \r\n ",  8},
        {" \r \n hoge \r \n ",  "hoge \r \n ", 9},
    };

    len = 1;
    mu_assert("empty string", skip_head_space("", &len) == NULL);
    mu_assert("empty string", len == 0);

    return skip_check_result(list, mu_numelem(list), skip_head_space);
}

static
const char *test_skip_tail_space()
{
    struct skip_testdata list[] = {
        {"",                    NULL,          0},
        {"hoge",                "hoge",        4},
        {"   hoge   ",          "   hoge",     7},
        {"hoge   ",             "hoge",        4},
        {"   hoge",             "   hoge",     7},
        {"   ho ge   ",         "   ho ge",    8},
        {"   ho\r\nge   ",      "   ho\r\nge", 9},
        {"\r\nhoge\r\n",        "\r\nhoge",    6},
        {"\r\n  hoge  \r\n",    "\r\n  hoge",  8},
        {"  \r\nhoge\r\n  ",    "  \r\nhoge",  8},
        {" \r\n hoge \r\n ",    " \r\n hoge",  8},
        {" \r \n hoge \r \n ",  " \r \n hoge", 9},
    };

    return skip_check_result(list, mu_numelem(list), skip_tail_space);
}

static
const char *test_cut_tail_space()
{
    struct tdom_text *text = tdom_text_new(NULL, NULL);

    mu_assert("param check", cut_tail_space(NULL) == 1);
    mu_assert("param check", cut_tail_space(text) == 1);
    tdom_text_delete(text);

    text = tdom_text_new(NULL, " \r \n ");
    mu_assert("cut_tail_space", cut_tail_space(text) == 0);
    mu_assert("text value", text->value != NULL);
    mu_assert("text value", strcmp("", text->value) == 0);
    tdom_text_delete(text);

    text = tdom_text_new(NULL, "text");
    mu_assert("cut_tail_space", cut_tail_space(text) == 0);
    mu_assert("text value", text->value != NULL);
    mu_assert("text value", strcmp("text", text->value) == 0);
    tdom_text_delete(text);

    text = tdom_text_new(NULL, "text \r \n ");
    mu_assert("cut_tail_space", cut_tail_space(text) == 0);
    mu_assert("text value", text->value != NULL);
    mu_assert("text value", strcmp("text", text->value) == 0);
    tdom_text_delete(text);

    return 0;
}

#define DEF_TEST_PARSER_PARSE(size) \
    static const char *test_parser_parse##size() { \
        struct tdom_doc *prev = doc; \
        const char *result = test_parser_parse((size)); \
        if (prev == NULL || result != 0) { \
            return result; \
        } else { \
            result = compare_elem(NULL, NULL, prev->root, doc->root); \
            tdom_doc_delete(prev); \
            return result; \
        } \
    }

DEF_TEST_PARSER_PARSE(1);
DEF_TEST_PARSER_PARSE(2);
DEF_TEST_PARSER_PARSE(8);
DEF_TEST_PARSER_PARSE(16);
DEF_TEST_PARSER_PARSE(32);
DEF_TEST_PARSER_PARSE(64);
DEF_TEST_PARSER_PARSE(128);
DEF_TEST_PARSER_PARSE(256);
DEF_TEST_PARSER_PARSE(512);

static
const char *all_tests()
{
    mu_run_test(test_ignore_space);
    mu_run_test(test_skip_head_space);
    mu_run_test(test_skip_tail_space);
    mu_run_test(test_cut_tail_space);
    mu_run_test(test_count_escaped_len);
    mu_run_test(test_escape);
    mu_run_test(test_unescape);

    mu_run_test(test_doc_new);
    mu_run_test(test_elem_new);
    mu_run_test(test_attr_new);
    mu_run_test(test_text_new);
    mu_run_test(test_ns_new);

    mu_run_test(test_doc_set);
    mu_run_test(test_append_child);
    mu_run_test(test_insert_after);
    mu_run_test(test_remove_child);
    mu_run_test(test_set_value);
    mu_run_test(test_get_attr);
    mu_run_test(test_set_attr);
    mu_run_test(test_remove_attr);
    mu_run_test(test_set_ns);
    mu_run_test(test_set_elem_value);

    mu_run_test(test_soap_new);
    mu_run_test(test_soap_set);
    mu_run_test(test_soap_set_pointer);
    mu_run_test(test_soap_get);
    mu_run_test(test_soap_err_new);
    mu_run_test(test_soap_get_upnp_err);

    mu_run_test(test_parser_parse1);
    mu_run_test(test_parser_parse2);
    mu_run_test(test_parser_parse8);
    mu_run_test(test_parser_parse16);
    mu_run_test(test_parser_parse32);
    mu_run_test(test_parser_parse64);
    mu_run_test(test_parser_parse128);
    mu_run_test(test_parser_parse256);
    mu_run_test(test_parser_parse512);

    mu_run_test(test_is_ns_default);
    mu_run_test(test_count_elem);
    mu_run_test(test_count_ns_prefix);
    mu_run_test(test_get_attr2);
    mu_run_test(test_get_value);
    mu_run_test(test_lookup_ns_uri);
    mu_run_test(test_lookup_ns_prefix);
    mu_run_test(test_get_elem);

    mu_run_test(test_doc_delete);

    mu_run_test(test_writer);

    return 0;
}

int main(int argc, char *argv[])
{
    const char *result = all_tests();
    if (result != 0) {
        printf("Failed: %s:%d\n", last_test, line);
        printf("%s\n", result);
    } else {
        printf("ALL TESTS PASSED\n");
        printf("  Tests run : %d\n", tests_run);
        printf("  Assertion : %d\n", assertion);
    }

#ifdef WIN32
    _getch();
#endif

    return result != 0;
}

